Skip to content

Throw conflict on upsert#610

Merged
abnegate merged 1 commit intomainfrom
feat-upsert-conflict
Jun 17, 2025
Merged

Throw conflict on upsert#610
abnegate merged 1 commit intomainfrom
feat-upsert-conflict

Conversation

@abnegate
Copy link
Copy Markdown
Member

@abnegate abnegate commented Jun 17, 2025

Summary by CodeRabbit

  • Bug Fixes
    • Improved concurrency control to prevent overwriting documents that have been updated since the request was initiated. Users will now receive a conflict error if attempting to update a document that has changed after their request timestamp.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jun 17, 2025

Walkthrough

A concurrency control mechanism was introduced within the createOrUpdateDocumentsWithIncrease method of the Database class. This addition checks if a document has been updated after a specified timestamp, throwing an exception if a conflict is detected, thereby preventing stale data from overwriting newer versions.

Changes

File(s) Change Summary
src/Database/Database.php Added concurrency check in createOrUpdateDocumentsWithIncrease to detect and handle conflicts based on $updatedAt timestamps.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Database
    participant ExceptionHandler

    Client->>Database: createOrUpdateDocumentsWithIncrease(doc, timestamp)
    Database->>Database: Validate document structure
    Database->>Database: Parse oldDoc.$updatedAt
    alt Parsing fails
        Database->>ExceptionHandler: Throw DatabaseException
    else Parsing succeeds
        alt oldDoc.$updatedAt > request timestamp
            Database->>ExceptionHandler: Throw ConflictException
        else No conflict
            Database->>Database: Proceed with create/update logic
        end
    end
    Database-->>Client: Result or Exception
Loading

Poem

A hop through the code, a check in the night,
Now updates won't clash, all versions are right.
If timestamps collide, a conflict appears,
Preventing old data from causing new fears.
With careful control, our database grows—
Safe from the woes that concurrency knows!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (2)
src/Database/Database.php (2)

4257-4260: Wrap DateTime construction in try/catch for consistency & safety

Other new conflict-detection blocks (updateDocuments, createOrUpdateDocumentsWithIncrease, delete*) correctly guard the new \DateTime() call with a try … catch.
Here the same call is left unguarded – an invalid or empty $updatedAt string will bubble up as a fatal Exception.

- $oldUpdatedAt = new \DateTime($old->getUpdatedAt());
+ try {
+     $oldUpdatedAt = new \DateTime($old->getUpdatedAt());
+ } catch (\Throwable $e) {
+     throw new DatabaseException($e->getMessage(), $e->getCode(), $e);
+ }

4430-4438: Conflict check is performed on the modified document – never fires

In this loop the variable $document is mutated before the timestamp comparison:

$new      = new Document(array_merge($document->getArrayCopy(), $updates->getArrayCopy()));
$document = $new;                 // <— updatedAt already overwritten (now)
...
$oldUpdatedAt = new \DateTime($document->getUpdatedAt());   // uses NEW timestamp

As a result the ConflictException cannot trigger because the freshly-set $updatedAt is always ≥ $this->timestamp.

Quick fix – capture and test the original instance:

foreach ($batch as &$document) {
-   $new = new Document(array_merge($document->getArrayCopy(), $updates->getArrayCopy()));
-   $document = $new;
-   try {
-       $oldUpdatedAt = new \DateTime($document->getUpdatedAt());
+   $originalUpdatedAt = $document->getUpdatedAt();
+
+   try {
+       $oldUpdatedAt = new \DateTime($originalUpdatedAt);
    } catch (Exception $e) {
       ...
    }
    if (!is_null($this->timestamp) && $oldUpdatedAt > $this->timestamp) {
        throw new ConflictException('Document was updated after the request timestamp');
    }
+
+   $new       = new Document(array_merge($document->getArrayCopy(), $updates->getArrayCopy()));
+   $document  = $new;

(or move the existing block above the re-assignment).

Without this fix stale-write protection is ineffective in updateDocuments.

🧹 Nitpick comments (1)
src/Database/Database.php (1)

5862-5869: Conflict gate for bulk deletes works but could be hardened

Implementation matches other sections. Consider extracting the repeated “parse-and-compare updatedAt” code into a small private helper to reduce duplication and ensure future fixes (like time-zone handling) are applied everywhere at once.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2bd87ac and b38ea71.

📒 Files selected for processing (1)
  • src/Database/Database.php (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Setup & Build Docker Image
  • GitHub Check: CodeQL
🔇 Additional comments (2)
src/Database/Database.php (2)

5030-5038: Looks good – identical guard added to upsert path

The timestamp-based conflict detection is implemented correctly here (uses $old, is wrapped in try … catch, and matches the logic used elsewhere).


5341-5349: Guard added to deleteDocument is sane

The new check mirrors the pattern used in creation/update paths and prevents accidental deletion of fresher data. No issues spotted.

@abnegate abnegate merged commit aa0116b into main Jun 17, 2025
35 of 39 checks passed
@abnegate abnegate deleted the feat-upsert-conflict branch June 17, 2025 23:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant